﻿namespace Behemoth.SharePoint.TimerJobs {

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Net;
    using System.Text;

    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Administration;

    using Behemoth.Configuration;
    using Behemoth.SharePoint;
    using Behemoth.SharePoint.Configuration;
    using Behemoth.SharePoint.Logging;
    using BSLogs = Behemoth.SharePoint.Logging;

    public class WebBindableTimerJob : SPJobDefinition {

        private ULSTraceLogging _ulsLog = null;
        protected ULSTraceLogging UlsLog {
            get {
                if (_ulsLog == null) {
                    _ulsLog = new ULSTraceLogging();
                    _ulsLog.DefaultProduct = this.LoggingProduct;
                    _ulsLog.DefaultCategory = this.LoggingCategory;
                }
                return _ulsLog;
            }
        }

        [Persisted]
        public string LoggingProduct = "Behemoth";

        [Persisted]
        public string LoggingCategory = "WebBindableTimerJob";

        [Persisted]
        private List<string> webUrls = null;
        protected List<string> WebUrls {
            get {
                if (webUrls == null)
                    webUrls = new List<string>();
                return webUrls;
            }
        }

        public WebBindableTimerJob()
            : base() {
        }
        public WebBindableTimerJob(
            string jobName,
            string jobTitle,
            SPWebApplication webApp,
            SPJobLockType lockType
        )
            : base(jobName, webApp, null, lockType) {
            this.Name = jobName;
            this.Title = jobTitle; 
        }

        /// <summary>
        /// If the web is not already stored in the job definition's
        /// properties, add it and (optionally) update the job definition.
        /// </summary>
        /// </summary>
        /// <param name="web"></param>
        /// <param name="doUpdate"></param>
        public void AddWeb(SPWeb web, bool doUpdate) {
            string url = web.Url;
            if (!this.WebUrls.Contains(url)) {
                this.WebUrls.Add(url);
                if (doUpdate)
                    this.Update(true);
            }
        }

        /// <summary>
        /// If the web is stored in the job definition's properties,
        /// remove it and (optionally) update the job definition.
        /// </summary>
        /// <param name="web"></param>
        /// <param name="doUpdate"></param>
        public void RemoveWeb(SPWeb web, bool doUpdate) {
            string url = web.Url;
            if (this.WebUrls.Contains(url)) {
                this.WebUrls.Remove(url);
                if (doUpdate)
                    this.Update(true);
            }
        }

        /// <summary>
        /// Gets a job definition by a specified job name
        /// </summary>
        /// <param name="web"></param>
        /// <param name="app"></param>
        /// <param name="jobName"></param>
        /// <param name="throwException"></param>
        /// <returns></returns>
        public static SPJobDefinition GetJobDefinition(SPWeb web, string jobName, bool throwException) {
            if (web == null)
                throw new ArgumentNullException("Expecting a valid SPWeb object instance.", "web");
            return GetJobDefinition(web.Site.WebApplication, jobName, throwException);
        }
        public static SPJobDefinition GetJobDefinition(SPWebApplication app, string jobName, bool throwException) {
            foreach (SPJobDefinition job in app.JobDefinitions) {
                if (job.Name == jobName)
                    return job;
            }
            if (throwException) {
                throw new ArgumentOutOfRangeException(
                    string.Format("Could not find job named '{0}' in WebApplication '{1}'.", jobName, app.Name),
                    "jobName"
                );
            }
            return null;
        }
        // TODO would this make a decent extension method?

        /// <summary>
        /// If the web is not already stored in the job definition's
        /// properties, add it and updates the job definition. (The caller 
        /// is responsible for creating the job definition if it does not 
        /// already exist.)
        /// </summary>
        /// <param name="job"></param>
        /// <param name="web"></param>
        public static void BindWeb(WebBindableTimerJob job, SPWeb web) {
            if (job == null)
                throw new ArgumentNullException("Expecting a timer job definition that is a subclass of WebBindableTimerJob.", "job");
            if (web == null)
                throw new ArgumentNullException("Expecting a valid SPWeb object instance.", "web");
            // attempt to add the web and update
            job.AddWeb(web, true);
        }
        /// <summary>
        /// Given a job definition, detach a web if it is attached.
        /// If it is the last web attached, the job can optionally be deleted.
        /// </summary>
        /// <param name="job"></param>
        /// <param name="web"></param>
        /// <returns>true if the job was deleted, otherwise false</returns>
        public bool UnbindWeb(SPWeb web, bool deleteIfLastWeb) {
            if (web == null)
                throw new ArgumentNullException("Expecting a valid SPWeb object instance.", "web");
            // attempt to remove the web and update
            this.RemoveWeb(web, false);
            /*
            // if this was the last web, delete the job
            if (this.webUrls.Count == 0 && deleteIfLastWeb) {
                this.Delete();
                // TODO this.Dispose();
                return true;
            } else {
                this.Update(true);
            }
             */
            return false;
        }

        /*
        // TODO get schedule string from web feature properties
         */

        /// <summary>
        /// Developer should override this method to provide
        /// functionality that will be performed on each web that is bound
        /// </summary>
        /// <param name="web"></param>
        public virtual void DoBoundWebAction(SPWeb web) {
            //new LoggingEventHandler(ulsLog.Log)
        }

        public override void Execute(Guid targetInstanceId) {
            if (this.webUrls == null) {
                UlsLog.Write("WebBindableTimerJob.Execute() had nothing to do, because webUrls is null.", BSLogs.TraceSeverity.Unexpected);
                return;
            }
            foreach (string url in this.webUrls) {
                try {
                    using (SPSite site = new SPSite(url)) {
                        using (SPWeb web = site.OpenWeb()) {
                            DoBoundWebAction(web);
                        }
                    }
                } catch (Exception ex) {
                    string msg = string.Format("Timer job '{0}' failed to run Execute for web '{1}'. Exception: {2} Stack Trace: {3}", this.Name, url, ex.Message, ex.StackTrace);
                    UlsLog.Write(msg, BSLogs.TraceSeverity.Exception);
                    UlsLog.Write(ex);
                    throw new Exception(msg); // , ex
                }
            }
        }

        /*
        public static void DeleteOldJobs(SPSite site) {
            // Make sure the job isn't already registered.
            foreach (SPJobDefinition job in site.WebApplication.JobDefinitions) {
                if (job.Name == Globals.JobName)
                    job.Delete();
            }
        }

        public static void CreateInstance(SPWeb web, ListContentTypeRefreshEventArgs args) {
            ContentTypeRefreshTimerJob.DeleteOldJobs(web.Site);
            ContentTypeRefreshTimerJob job = new ContentTypeRefreshTimerJob(web.Site.WebApplication);
            job.WebUrl = web.Url;
            job.RefreshArgs = args;
            job.Schedule = new SPOneTimeSchedule(DateTime.Now.AddSeconds(30));
            job.Update();
        }
         */

    } // class

}
